from math import log, sin, cos, radians, tan, exp, asin, acos, atan, factorial
import re

# 简单运算
def calculate(n1, n2, operator):
    '''
    :param n1: float
    :param n2: float
    :param operator: + - * / ^
    :return: float
    '''
    result = 0
    if operator == "+":
        result = n1 + n2
    if operator == "-":
        result = n1 - n2
    if operator == "*":
        result = n1 * n2
    if operator == "/":
        result = n1 / n2
    if operator == "^":
        result = float(n1) ** float(n2)
    return result
 
 
# 判断是否是运算符，如果是返回True
def is_operator(e):
    opers = ['+', '-', '*', '/', '(', ')', "^"]
    return True if e in opers else False
 
 
# 将算式处理成列表，解决横杠是负数还是减号
def formula_format(formula):
    # 去空格
    formula = re.sub(' ', '', formula)
    formula_list = [i for i in re.split('(\-\d+\.?\d*)', formula) if i]
    # 最终的算式列表
    final_formula = []
    for item in formula_list:
        
        if len(final_formula) == 0 and re.search('^\-\d+\.?\d*$', item):
            final_formula.append(item)
            continue
 
        if len(final_formula) > 0:
            # 如果final_formal最后一个元素是运算符
            # ['+', '-', '*', '/', '('], 则横杠数字不是负数
            if re.search('[\+\-\*\/\(\^]$', final_formula[-1]):
                final_formula.append(item)
                continue
        # 按照运算符分割
        item_split = [i for i in re.split('([\+\-\*\/\(\)\^])', item) if i]
        final_formula += item_split
    
    return final_formula
 
def decision(tail_op, now_op):
    # 定义4种运算符级别
    rate1 = ['+', '-']
    rate2 = ['*', '/']
    rate3 = ["^"]
    rate4 = ['(']
    rate5 = [')']
    
 
    if tail_op in rate1:
        if now_op in rate2 or now_op in rate4:
            # 说明连续两个运算优先级不一样，需要入栈
            return -1
        else:
            return 1
    elif tail_op in rate2:
        if now_op in rate3 or now_op in rate4:
            return -1
        else:
            return 1
    elif tail_op in rate3:
        if now_op in rate4:
            return -1
        else:
            return 1

    elif tail_op in rate4:
        if now_op in rate5:
            return 0   # ( 遇上 ) 需要弹出 (，丢掉 )
        else:
            return -1  # 只要栈顶元素为(，当前元素不是)都应入栈。
    else:
        return -1
        
 
 
def final_calc(formula_list):
    num_stack = []       # 数字栈
    op_stack = []        # 运算符栈
    for e in formula_list:
        operator = is_operator(e)
        if not operator:
            num_stack.append(float(e))
        else:
            # 如果是运算符
            while True:
                # 如果运算符栈等于0：入栈
                if len(op_stack) == 0:
                    op_stack.append(e)
                    break
                # 判断是压入还是弹出
                tag = decision(op_stack[-1], e)
                if tag == -1:
                    # 如果是-1压入运算符栈进入下一次循环
                    op_stack.append(e)
                    break
                elif tag == 0:
                    # 如果是0弹出运算符栈内最后一个(, 丢掉当前)，进入下一次循环
                    op_stack.pop()
                    break
                elif tag == 1:
                    # 如果是1弹出运算符栈内最后两个元素，弹出数字栈最后两位元素。
                    op = op_stack.pop()
                    num2 = num_stack.pop()
                    num1 = num_stack.pop()
                
                    num_stack.append(calculate(num1, num2, op))
    # 处理大循环结束后 数字栈和运算符栈中可能还有元素 的情况
    try:
        while len(op_stack) != 0:
            op = op_stack.pop()
            num2 = num_stack.pop()
            num1 = num_stack.pop()
            
            num_stack.append(calculate(num1, num2, op))
    except Exception:
        return False, "Check Brackets!"
    return num_stack, op_stack


def SimpleCal(formula):
    formula_list = formula_format(formula)
    result, appendix = final_calc(formula_list)
    if isinstance(result, bool):
        return appendix
    return result[0]

# 检测下一个括号
def get_next_brack(sentence):
    cnt_left = 0
    for i in range(len(sentence)):
        if sentence[i] == "(":
            cnt_left += 1
        elif sentence[i] == ")":
            cnt_left -= 1
        if cnt_left == -1:
            return i
    return len(sentence) - 1

# Factorial: Try to get Front Number
def get_pre_brack(sentence):
    cnt_right = len(sentence) - 1
    if cnt_right < 0:
        return "Factorial in Head!"
    while cnt_right >= 0:
        if sentence[cnt_right] in "0123456789":
            cnt_right -= 1
        else:
            if sentence[cnt_right] == '.':
                return "Factorial not Integer!"
            
            return cnt_right + 1
    return cnt_right + 1
    
    # while sentence[]



def Main_Calculator(formula):

    if "ln(" not in formula and "sin(" not in formula and "cos(" not in formula \
        and "tan(" not in formula and "!" not in formula:
        return SimpleCal(formula)

    Drop = False
    Failure = ""

    while not Drop and "!" in formula:
        # break
        Ending = formula.index("!")
        Start = get_pre_brack(formula[: Ending])

        if isinstance(Start, str):
            # print(Start)
            Drop = True
            Failure = Start
            return Failure
        
        Inside = factorial(int(formula[Start: Ending]))
        formula = formula.replace(formula[Start: Ending + 1], str(Inside), 1)


    while not Drop and "ln(" in formula:
        Start = formula.index("ln(")
        Ending = get_next_brack(formula[Start + 3:]) 
        Inside = Main_Calculator(formula[Start + 3: Start + Ending + 3])
        Inside = log(Inside)
        formula = formula.replace(formula[Start : Start + Ending + 4], str(Inside),1)


    while not Drop and "arcsin(" in formula:
        Start = formula.index("arcsin(")
        Ending = get_next_brack(formula[Start + 7:]) 
        Inside = Main_Calculator(formula[Start + 7 :Start + Ending + 7])
        try:
            Inside = asin(Inside)
        except Exception :
            Drop = True
            Failure = f"Check arcsin Function in Pos {Start}"
            return Failure
        formula = formula.replace(formula[Start : Start + Ending + 8], str(Inside), 1)

    while not Drop and "arccos(" in formula :
        Start = formula.index("arccos(")
        Ending = get_next_brack(formula[Start + 7:]) 
        Inside = Main_Calculator(formula[Start + 7 :Start + Ending + 7])
        Inside = acos(Inside)
        formula = formula.replace(formula[Start : Start + Ending + 8], str(Inside), 1)
    
    while not Drop and "arctan(" in formula:
        Start = formula.index("arctan(")
        Ending = get_next_brack(formula[Start + 7:]) 
        Inside = Main_Calculator(formula[Start + 7 :Start + Ending + 7])
        Inside = atan(Inside)
        formula = formula.replace(formula[Start : Start + Ending + 8], str(Inside), 1)

    while not Drop and "sin(" in formula:
        Start = formula.index("sin(")
        Ending = get_next_brack(formula[Start + 4:]) 
        Inside = Main_Calculator(formula[Start + 4: Start + Ending + 4])
        Inside = sin(radians(Inside))
        formula = formula.replace(formula[Start : Start + Ending + 5], str(Inside),1)
        
    while not Drop and "cos(" in formula:
        Start = formula.index("cos(")
        Ending = get_next_brack(formula[Start + 4:]) 
        Inside = Main_Calculator(formula[Start + 4: Start + Ending + 4])
        Inside = cos(radians(Inside))
        formula = formula.replace(formula[Start : Start + Ending + 5], str(Inside),1)

    while not Drop and "tan(" in formula:
        Start = formula.index("tan(")
        Ending = get_next_brack(formula[Start + 4:]) 
        Inside = Main_Calculator(formula[Start + 4: Start + Ending + 4])
        Inside = tan(radians(Inside))
        formula = formula.replace(formula[Start : Start + Ending + 5], str(Inside),1)
    
    while not Drop and "exp(" in formula:
        Start = formula.index("exp(")
        Ending = get_next_brack(formula[Start + 4:]) 
        Inside = Main_Calculator(formula[Start + 4: Start + Ending + 4])
        Inside = exp(Inside)
        formula = formula.replace(formula[Start : Start + Ending + 5], str(Inside),1)
    
    if Drop:
        return Failure

    return SimpleCal(formula)
    

if __name__ == "__main__":
    Tests = [
    "1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 *568/14 )) - (-4*3)/ (16-3*2))",
    "ln(5^3)*ln(5^3) - (-4) + ln(41+ln(38-4) +  sin(3.3))",
    "ln(34-0.003+tan(32)) + cos(24) * 7",
    "exp(2+sin(21)) + cos(ln(21))",
    "sin(-34) + 34 *4^2",
    "2*3^(-2)",
    "2^(ln(3))",
    # "arcsin(0.53 + arccos(-0.02)) + 2 + arctan(21) + arccos(0.2)"
    "arcsin(arccos(-0.55) / 51 + 0.53)",
    "(sin(21) +(3)+ (1+2*(3+4)))",
    "-4!"
    ]
    True_answer = [
    "1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 *568/14 )) - (-4*3)/ (16-3*2))",
    "log(5^3)*log(5^3) - (-4) + log(41+log(38-4) +  sin(radians(3.3)))",
    "log(34-0.003+tan(radians(32))) + cos(radians(24)) * 7",
    "exp(2+sin(radians(21))) + cos(radians(log(21) ))",
    "sin(radians(-34 )) + 34*4**2",
    "2*3**(-2)",
    "2^(4+5)",
    # "asin(0.53) + 2 + atan(21) + acos(0.2)"
    "asin(acos(-0.55)/51 + 0.53)",
    "asin(0.1)",
    "factorial(5)"
    ]
    for Test, Answer in zip(Tests, True_answer):
        print(f"Manual Calculator:{Main_Calculator(Test)}, 真实结果:{eval(Answer)}", end = "\n")
    # print(factorial("4"))
    

